/*
 * Lianhaidong <lianhaidong@gmail.com>
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "utils/easy_config.h"
#include "utils/debug.h"

enum word_type {
	WORD_CONTAINER,
	WORD_DIGIT,
	WORD_STRING,
	WORD_CHAR, 
	WORD_BOOL,

	WORD_EQ,	
	WORD_BEGIN,
	WORD_END,
	WORD_SEMI,
	WORD_ID,
};


	
static char * word_array[] =
{
	"container",
	"digit",
	"string",
	"char",
	"bool",
	"=",
	"{",
	"}",
	";"
};	


static struct easy_config_node *easy_config_node_alloc()
{
	struct easy_config_node *rv;

	rv = malloc(sizeof(*rv));
	if (!rv) {
		msg("Outof memory !!!");
		return NULL;
	}

	memset(rv, 0, sizeof(*rv));

	INIT_LIST_HEAD(&rv->link);
	INIT_LIST_HEAD(&rv->sub_head);
	pthread_mutex_init(&rv->lock, 0);

	return rv;
}

struct word {
	enum word_type type;
	char *str;
	int line;
};


static inline int easy_is_space(char c)
{
	if (c == ' ' || c == '\t')
		return 1;

	return 0;
}

static enum word_type easy_get_type(char *buf)
{
	int i;

	for (i = 0; i < WORD_ID; i++) {
		if (strcmp(buf, word_array[i]) == 0)
			return i;
	}

	return WORD_ID;
}

static int my_getc(FILE *fp) 
{
	int c;

loop:
	c =   fgetc(fp);

	if (c == '\r') 
		goto loop;

	return c;
}

static int get_word(FILE *fp, struct word *w)
{
	char c;
	char buf[1024] = "\0";	
	int pos = 0;
	static int line = 0;

	do {
		c = my_getc(fp);
	} while (easy_is_space(c) || c == '\n');

loop:
	buf[pos++] = c;
	c = my_getc(fp);

	if (c == '\n') {
		w->line = line;
		line ++;
		goto ok;
	}
	if (easy_is_space(c)) {
		buf[pos] = '\0';
		w->line = line;
		goto ok;
	}

	goto loop;

ok:
	w->type = easy_get_type(buf);
	w->str = strdup(buf);

	return 0;
}


static struct easy_config_node *easy_config_create_char(FILE *fp, int layer)
{
	static struct easy_config_node *rv = NULL;
	struct word w;

	get_word(fp, &w);
	if (w.type != WORD_ID) {
		msg("%s type is not id: %d", w.str, w.line);
		goto out;
	}
	
	rv = easy_config_node_alloc();
	rv->name = w.str;
	rv->type = EASY_CONFIG_CHAR;
	rv->layer = layer;

	get_word(fp, &w);
	if (w.type != WORD_EQ) {
		msg("%s type is not eq: %d", w.str, w.line);
		goto out;
	}	
	free (w.str);

	get_word(fp, &w);
	if (w.type != WORD_ID) {
		msg("%s type is not id: %d", w.str, w.line);
		goto out;
	}
	rv->data = malloc(sizeof(int));
	*(int *)(rv->data) = atoi(w.str);
	free(w.str);

	get_word(fp, &w);
	if (w.type != WORD_SEMI) {
		msg("%s type is not semi : %d", w.str, w.line);
		goto out;
	}

	free(w.str);
	return rv;
out:	
	free(w.str);
	return NULL;
}


static struct easy_config_node *easy_config_create_bool(FILE *fp, int layer)
{
	static struct easy_config_node *rv = NULL;
	struct word w;

	get_word(fp, &w);
	if (w.type != WORD_ID) {
		msg("%s type is not id: %d", w.str, w.line);
		goto out;
	}
	
	rv = easy_config_node_alloc();
	rv->name = w.str;
	rv->type = EASY_CONFIG_BOOL;
	rv->layer = layer;

	get_word(fp, &w);
	if (w.type != WORD_EQ) {
		msg("%s type is not eq: %d", w.str, w.line);
		goto out;
	}	
	free (w.str);

	get_word(fp, &w);
	if (w.type != WORD_ID) {
		msg("type is not id");
		goto out;
	}
	rv->data = malloc(sizeof(int));
	*(int *)(rv->data) = atoi(w.str);
	free(w.str);

	get_word(fp, &w);
	if (w.type != WORD_SEMI) {
		msg("%s type is not semi: %d", w.str, w.line);
		goto out;
	}

	free(w.str);
	return rv;
out:	
	free(w.str);
	return NULL;
}


static struct easy_config_node *easy_config_create_digit(FILE *fp, int layer)
{
	static struct easy_config_node *rv = NULL;
	struct word w;

	get_word(fp, &w);
	if (w.type != WORD_ID) {
		msg("%s type is not id: %d", w.str, w.line);
		goto out;
	}
	
	rv = easy_config_node_alloc();
	rv->name = w.str;
	rv->type = EASY_CONFIG_DIGIT;
	rv->layer = layer;

	get_word(fp, &w);
	if (w.type != WORD_EQ) {
		msg("%s type is not eq: %d", w.str, w.line);
		goto out;
	}	
	free (w.str);

	get_word(fp, &w);
	if (w.type != WORD_ID) {
		msg("%s type is not id: %d", w.str, w.line);
		goto out;
	}

	rv->data = malloc(sizeof(int));
	*(int *)(rv->data) = atoi(w.str);
	free(w.str);

	get_word(fp, &w);
	if (w.type != WORD_SEMI) {
		msg("%s type is not semi: %d", w.str, w.line);
		goto out;
	}

	free(w.str);

	return rv;
out:	
	free(w.str);
	return NULL;
}

static struct easy_config_node *easy_config_create_string(FILE *fp, int layer)
{
	static struct easy_config_node *rv = NULL;
	struct word w;

	get_word(fp, &w);
	if (w.type != WORD_ID) {
		msg("%s type is not id: %d", w.str, w.line);
		goto out;
	}
	
	rv = easy_config_node_alloc();
	rv->name = w.str;
	rv->type = EASY_CONFIG_STRING;
	rv->layer = layer;

	get_word(fp, &w);
	if (w.type != WORD_EQ) {
		msg("%s type is not eq: %d", w.str, w.line);
		goto out;
	}	
	free (w.str);

	get_word(fp, &w);
	if (w.type != WORD_ID) {
		msg("%s type is not id: %d", w.str, w.line);
		goto out;
	}
	rv->data = w.str;

	get_word(fp, &w);
	if (w.type != WORD_SEMI) {
		msg("%s type is not semi: %d", w.str, w.line);
		goto out;
	}

	free(w.str);
	return rv;
out:	
	free(w.str);
	return NULL;
}

static struct easy_config_node *easy_config_create_container(FILE *fp, int layer)
{
	struct easy_config_node *rv = NULL, *sub;
	struct word w;

	rv = easy_config_node_alloc();
	if (!rv) {
		msg("Outof memory !!!");
		return NULL;
	}

	
	rv->type = EASY_CONFIG_CONTAINER;
	rv->data = w.str;
	rv->layer = layer;

	get_word(fp, &w);
	if (w.type != WORD_ID) {
		msg("%s type is not id: %d", w.str, w.line);
		goto fail_out;
	}	
	rv->name = w.str;

	get_word(fp, &w);
	if (w.type != WORD_BEGIN) {
		msg("%s type is not begin: %d", w.str, w.line);
		goto fail_out;
	}
	free(w.str);	

	get_word(fp, &w);

loop:
	switch (w.type) {
	case WORD_CONTAINER:
		sub = easy_config_create_container(fp, layer + 1);
		break;
	case WORD_STRING:
		sub = easy_config_create_string(fp, layer + 1);
		break;
		
	case WORD_DIGIT:
		sub = easy_config_create_digit(fp, layer + 1);
		break;
	case WORD_CHAR:
		sub = easy_config_create_char(fp, layer + 1);
		break;
	case WORD_BOOL:
		sub = easy_config_create_bool(fp, layer + 1);
		break;
	default:
		msg("%s type is invalid: %d", w.str, w.line);
		free(w.str);
		goto fail_out;
	}
	
	
	assert(sub);

	list_add_tail(&sub->link, &rv->sub_head);
	free(w.str);

	get_word(fp, &w);
	if (w.type == WORD_END) {
		free(w.str);
		return rv;
	}
	goto loop;

fail_out:
	return NULL;
}

struct easy_config_node *load_config_from_file(char *file_name)
{
	FILE *fp = fopen(file_name, "r");
	struct easy_config_node *rv = NULL;
	struct word w;

	if (!fp) {
		msg("Can not open file %s", file_name);	
		return NULL;
	}
	
	get_word(fp, &w);
	if (w.type != WORD_CONTAINER) {
		msg("%s type is not container: line = %d", w.str, w.line);
		goto out;
	}	
	free(w.str);
	
	rv =  easy_config_create_container(fp, 0);
	assert(rv);
out:
	fclose(fp);
	return rv;		
}

#define print_tab(fp, n) do { 	\
	int __i;	\
	for (__i = 0; __i < n; __i ++) 	\
		fprintf(fp, "\t");	\
} while (0)

static int save_container(struct easy_config_node *n, FILE *fp) 
{
	struct easy_config_node *sub;

	fprintf(fp, "%s {\n", n->name);

	list_for_each_entry(sub, &n->sub_head, link) {
		if (sub->type == EASY_CONFIG_CONTAINER)
			fprintf(fp, "\n");
		print_tab(fp, sub->layer);
		switch (sub->type) {
		case EASY_CONFIG_CONTAINER:
			fprintf(fp, "container ");
			save_container(sub, fp);
			break;
		case EASY_CONFIG_STRING:
			fprintf(fp, "string %s = %s ;\n", sub->name, (char *)sub->data);
			break;
		case EASY_CONFIG_DIGIT:
			fprintf(fp, "digit %s = %d ;\n", sub->name, *(int *)sub->data);
			break;
		case EASY_CONFIG_BOOL:
			fprintf(fp, "bool %s = %d ;\n", sub->name, *(int *)sub->data);
			break;
		case EASY_CONFIG_CHAR:
			fprintf(fp, "char %s = %c ;\n", sub->name, *(char *)sub->data);
			break;
		default:
			msg("invalid type %d", sub->type);
			return -1;
		}
	}

	print_tab(fp, n->layer);
	fprintf(fp, "}\n");

	return 0;
}

int save_config_to_file(struct easy_config_node *root, char *file_name)
{
	FILE *fp;

	fp = fopen(file_name, "w");
	if (!fp) {
		msg("Can not open file %s", file_name);
		return -1;
	}
	fprintf(fp, "container ");
	save_container(root, fp);
	fflush(fp);
	return 0;
}


void easy_config_release(struct easy_config_node *root)
{
	struct easy_config_node *pos, *n;

	list_for_each_entry_safe(pos, n, &root->sub_head, link) {
		if (pos->type == EASY_CONFIG_CONTAINER) {
			easy_config_release(pos);
		} else {
			pthread_mutex_destroy(&pos->lock);
			free(pos->name);
			free(pos->data);
			free(pos);
		}
	}

	free(root->name);
	free(root);
}

struct easy_config_node *easy_config_get_node(struct easy_config_node *father, char *name)
{
	struct easy_config_node *rv;	

	list_for_each_entry(rv, &father->sub_head, link) {
		if (strcmp(name, rv->name) == 0) 
			return rv;
	}

	return NULL;
}


int easy_config_get_val(struct easy_config_node *n, void *val)
{
	int rv = 0;

	pthread_mutex_lock(&n->lock);
	switch (n->type) {
	case EASY_CONFIG_CONTAINER:
		return -1;
	case EASY_CONFIG_STRING:
		strcpy(val, n->data);
		break;
	case EASY_CONFIG_DIGIT:
		*(int *)val = *(int *)n->data;
		break;
	case EASY_CONFIG_CHAR:
		*(char *)val = *(char *)n->data;
		break;
	case EASY_CONFIG_BOOL:
		*(int *)val = *(int *)n->data;
		break;
	default:
		msg("unknown type ");
		rv = -1;
	}

	pthread_mutex_unlock(&n->lock);

	return 0;
}



int easy_config_set_val(struct easy_config_node *n, void *val)
{
	int rv = 0;
	pthread_mutex_lock(&n->lock);
	switch (n->type) {
	case EASY_CONFIG_CONTAINER:
		return -1;
	case EASY_CONFIG_STRING:
		free(n->data);
		n->data = strdup(val);
		break;
	case EASY_CONFIG_DIGIT:
		*(int *)n->data = *(int *)val;
		break;
	case EASY_CONFIG_CHAR:
		*(char *)n->data = *(char *)val;
		break;
	case EASY_CONFIG_BOOL:
		*(int *)n->data = *(int *)val;
		break;
	default:
		msg("unknown type ");
		rv = -1;
	}

	pthread_mutex_unlock(&n->lock);
	return rv;
}


int easy_config_add_sub(struct easy_config_node *node, enum easy_config_type type, char *name, void *val)
{
	struct easy_config_node *n;

	n = easy_config_node_alloc();
	assert(n);
	list_add_tail(&node->sub_head, &n->link);
	n->type = type;
	n->layer = node->layer + 1;
	n->name = strdup(name);
	n->data = val;
	
	return 0;
}

int easy_config_del_sub(struct easy_config_node *node, char *name)
{
	struct easy_config_node *sub;
	easy_config_for_each_sub(node, sub) {
		if (strcmp(name, sub->name) == 0) {
			list_del(&sub->link);
			return 0;
		}
	}

	return -1;
}


#ifdef EASY_CONFIG_STUB

int main(int argc, char **argv)
{
	struct easy_config_node *root;
	if (argc != 2) {
		msg("easy_config  config_file_name");
		return -1;
	}

	root = load_config_from_file(argv[1]);

	if (!root) {
		msg("load_config_from_file fail");
		return -1;
	}	

	save_config_to_file(root, argv[1]);	
	
	return 0;
}

#endif	/* EASY_CONFIG_STUB */



